package com.hospital.register.core.repository.impl;

import com.hospital.register.common.exception.BusinessException;
import com.hospital.register.common.result.ResultCodeEnum;
import com.hospital.register.common.utils.DateUtil;
import com.hospital.register.constants.BaseEntityConstants;
import com.hospital.register.constants.ScheduleConstants;
import com.hospital.register.core.repository.ScheduleRepository;
import com.hospital.register.core.service.DepartmentService;
import com.hospital.register.core.service.HospitalService;
import com.hospital.register.dto.core.api.ScheduleRemoveDto;
import com.hospital.register.dto.core.common.ScheduleDaysDto;
import com.hospital.register.dto.core.common.SchedulePageDto;
import com.hospital.register.dto.mongo.MongoCountDto;
import com.hospital.register.model.core.Department;
import com.hospital.register.model.core.Schedule;
import com.hospital.register.vo.core.api.ScheduleDetailVo;
import com.hospital.register.vo.core.common.HospitalQueryVo;
import com.hospital.register.vo.core.common.ScheduleDayVo;
import com.hospital.register.vo.core.common.ScheduleDaysVo;
import com.hospital.register.vo.core.common.ScheduleQueryVo;
import com.hospital.register.vo.order.OrderMessageVo;
import com.mongodb.client.result.UpdateResult;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;

import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;

@Repository
public class ScheduleRepositoryImpl implements ScheduleRepository {

    /**
     * 访问 Mongo
     */
    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 处理医院相关业务
     */
    @Autowired
    private HospitalService hospitalService;

    /**
     * 处理科室相关业务
     */
    @Autowired
    private DepartmentService departmentService;

    @Override
    public boolean existed(String hoscode, String hosScheduleId) {
        Criteria criteria = Criteria.where(ScheduleConstants.HOS_SCHEDULE_ID).is(hosScheduleId)// 排班 id
                .and(ScheduleConstants.HOSCODE).is(hoscode)// 医院编号
                .and(BaseEntityConstants.FIELD_IS_DELETED).is(BaseEntityConstants.NOT_DELETED);// 未删除
        Query query = Query.query(criteria);
        return this.mongoTemplate.exists(query, ScheduleConstants.COLLECTION_NAME);
    }

    @Override
    public boolean insert(Schedule schedule) {
        return this.mongoTemplate.insert(schedule) != null;
    }

    @Override
    public boolean update(Schedule schedule) {
        Criteria criteria = Criteria.where(ScheduleConstants.HOS_SCHEDULE_ID).is(schedule.getHosScheduleId())// 排班 id
                .and(ScheduleConstants.HOSCODE).is(schedule.getHoscode())// 医院编号
                .and(BaseEntityConstants.FIELD_IS_DELETED).is(BaseEntityConstants.NOT_DELETED);// 未删除
        Query query = Query.query(criteria);

        Update update = new Update();

        if (schedule.getTitle() != null) {
            update.set(ScheduleConstants.TITLE, schedule.getTitle());// 职称
        }

        if (schedule.getDocname() != null) {
            update.set(ScheduleConstants.DOCNAME, schedule.getDocname());// 医生名称
        }

        if (schedule.getSkill() != null) {
            update.set(ScheduleConstants.SKILL, schedule.getSkill());// 擅长技能
        }

        if (schedule.getWorkDate() != null) {
            update.set(ScheduleConstants.WORK_DATE, schedule.getWorkDate());// 排班日期
        }

        if (schedule.getWorkTime() != null) {
            update.set(ScheduleConstants.WORK_TIME, schedule.getWorkTime());// 排班时间
        }

        if (schedule.getReservedNumber() != null) {
            update.set(ScheduleConstants.RESERVED_NUMBER, schedule.getReservedNumber());// 可预约数
        }

        if (schedule.getAvailableNumber() != null) {
            update.set(ScheduleConstants.AVAILABLE_NUMBER, schedule.getAvailableNumber());// 剩余预约数
        }

        if (schedule.getAmount() != null) {
            update.set(ScheduleConstants.AMOUNT, schedule.getAmount());// 挂号费
        }

        if (schedule.getStatus() != null) {
            update.set(ScheduleConstants.STATUS, schedule.getStatus());// 状态
        }

        if (schedule.getUpdateTime() != null) {
            update.set(BaseEntityConstants.UPDATE_TIME, schedule.getUpdateTime());// 更新时间
        }

        // 科室编号不更新

        UpdateResult updateResult = this.mongoTemplate.updateFirst(query, update, ScheduleConstants.COLLECTION_NAME);
        return updateResult.wasAcknowledged();
    }

    @Override
    public Page<ScheduleQueryVo> findPage(SchedulePageDto dto) {
        Criteria criteria = Criteria.where(ScheduleConstants.HOSCODE).is(dto.getHoscode());// 医院编号

        if (dto.getDepcode() != null && dto.getDepcode().length() > 0) {
            criteria.and(ScheduleConstants.DEPCODE).is(dto.getDepcode());// 科室编号
        }

        if (dto.getWorkDate() != null) {
            criteria.and(ScheduleConstants.WORK_DATE).is(dto.getWorkDate());// 日期
        }

        criteria.and(BaseEntityConstants.FIELD_IS_DELETED).is(BaseEntityConstants.NOT_DELETED);// 未删除

        Query query = Query.query(criteria);
        long total = this.mongoTemplate.count(query, ScheduleConstants.COLLECTION_NAME);
        Pageable pageable = PageRequest.of(dto.getPage() - 1, dto.getLimit());
        query.with(pageable);

        List<ScheduleQueryVo> vos = this.mongoTemplate.find(query, ScheduleQueryVo.class,
                ScheduleConstants.COLLECTION_NAME);
        Page<ScheduleQueryVo> page = new PageImpl<>(vos, pageable, total);
        return page;
    }

    @Override
    public boolean remove(ScheduleRemoveDto dto) {
        Criteria criteria = Criteria.where(ScheduleConstants.HOS_SCHEDULE_ID).is(dto.getHosScheduleId())// 排班 id
                .and(ScheduleConstants.HOSCODE).is(dto.getHoscode())// 医院编号
                .and(BaseEntityConstants.FIELD_IS_DELETED).is(BaseEntityConstants.NOT_DELETED);// 未删除
        Query query = Query.query(criteria);

        Update update = Update.update(BaseEntityConstants.FIELD_IS_DELETED, BaseEntityConstants.DELETED)// 已删除
                .set(BaseEntityConstants.UPDATE_TIME, new Date());

        UpdateResult result = this.mongoTemplate.updateFirst(query, update, ScheduleConstants.COLLECTION_NAME);
        return result.wasAcknowledged();
    }

    @Override
    public boolean existInFuture(String hoscode, String depcode) {
        Criteria criteria = Criteria.where(ScheduleConstants.DEPCODE).is(depcode)// 科室编号
                .and(ScheduleConstants.HOSCODE).is(hoscode)// 医院编号
                .and(BaseEntityConstants.FIELD_IS_DELETED).is(BaseEntityConstants.NOT_DELETED)// 未删除
                .and(ScheduleConstants.STATUS).is(ScheduleConstants.STATUS_REGISTRABLE)// 可预约的
                .and(ScheduleConstants.WORK_DATE).gte(new Date());// 今天以后的
        Query query = Query.query(criteria);
        return this.mongoTemplate.exists(query, ScheduleConstants.COLLECTION_NAME);
    }

    @Override
    public ScheduleDaysVo findDataOfDays(ScheduleDaysDto conditions) {
        HospitalQueryVo hospital = this.hospitalService.findByHoscode(conditions.getHoscode());// 该科室所在医院
        if (hospital == null) {
            throw new BusinessException("医院编号错误", ResultCodeEnum.PARAM_ERROR.getCode());
        }

        if (hospital.getBookingRule() == null) {
            throw new BusinessException("所在医院的预约规则不存在", ResultCodeEnum.DATA_ERROR.getCode());
        }

        Department theirDepartment = this.departmentService.findOne(conditions.getHoscode(), conditions.getDepcode());
        if (theirDepartment == null) {
            throw new BusinessException("科室编号错误", ResultCodeEnum.PARAM_ERROR.getCode());
        }

        Criteria criteria = Criteria.where(ScheduleConstants.DEPCODE).is(conditions.getDepcode())// 所在科室的编号
                .and(ScheduleConstants.HOSCODE).is(conditions.getHoscode());// 所在医院的编号

        if (conditions.getWorkDates() != null && conditions.getWorkDates().size() > 0) {
            criteria.and(ScheduleConstants.WORK_DATE).in(conditions.getWorkDates());
        }

        criteria.and(BaseEntityConstants.FIELD_IS_DELETED).is(BaseEntityConstants.NOT_DELETED);// 未删除的
        Byte total = hospital.getBookingRule().getCycle();

        Calendar calendar = Calendar.getInstance();
        int thisYear = calendar.get(Calendar.YEAR);
        int thisMonth = calendar.get(Calendar.MONTH) + 1;

        ScheduleDaysVo result = new ScheduleDaysVo(hospital.getHosname(), total, Collections.EMPTY_LIST);
        ScheduleDaysVo.DepartmentInfo departmentResult = result.getBaseMap();
        departmentResult.setBigname(theirDepartment.getBigname());// 大科室名称
        departmentResult.setDepname(theirDepartment.getDepname());// 科室名称
        departmentResult.setWorkDateString(thisYear + "年" + thisMonth + "月");
        departmentResult.setReleaseTime(hospital.getBookingRule().getReleaseTime());// 医院的放号时间
        departmentResult.setStopTime(hospital.getBookingRule().getStopTime());// 医院的停号时间

        Byte zero = (byte) 0;
        if (total == null || total.equals(zero)) {
            return result;
        }

        MatchOperation matchOperation = Aggregation.match(criteria);
        GroupOperation groupOperation = Aggregation.group(ScheduleConstants.WORK_DATE)// 已工作日期分组
                .first(ScheduleConstants.WORK_DATE).as(ScheduleConstants.WORK_DATE)// 工作日期
                .count().as(ScheduleDayVo.DOC_COUNT)// 就诊医生数
                .sum(ScheduleConstants.AVAILABLE_NUMBER).as(ScheduleConstants.AVAILABLE_NUMBER)// 可用预约数
                .sum(ScheduleConstants.RESERVED_NUMBER).as(ScheduleConstants.RESERVED_NUMBER);// 剩余预约数

        SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, ScheduleConstants.WORK_DATE);// 以工作日期逆序
        SkipOperation skipOperation = Aggregation.skip((conditions.getPage() - 1) * conditions.getLimit());
        LimitOperation limitOperation = Aggregation.limit(conditions.getLimit());

        Aggregation aggregation = Aggregation.newAggregation(matchOperation, groupOperation, sortOperation,
                skipOperation, limitOperation);
        AggregationResults<ScheduleDayVo> aggregationResults = this.mongoTemplate.aggregate(aggregation,
                ScheduleConstants.COLLECTION_NAME, ScheduleDayVo.class);
        List<ScheduleDayVo> schedules = aggregationResults.getMappedResults();

        // 为每天的排班数据填充其它值
        if (schedules == null || schedules.isEmpty()) {
            return result;
        }

        result.setBookingScheduleRuleList(schedules);
        result.setBookingScheduleList(schedules);// 两个接口对这个属性的称呼不同

        for (ScheduleDayVo schedule : schedules) {
            calendar.setTimeInMillis(schedule.getWorkDate().getTime());
            int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
            schedule.setDayOfWeek(DateUtil.getChineseDayOfWeek(dayOfWeek));

            schedule.setWorkDateMd(schedule.getWorkDate());
            schedule.setStatus(0);
            // 接口中的状态值似乎和实体类中的不一样
            // 状态 0：正常 1：即将放号 -1：当天已停止挂号
        }

        return result;
    }

    /**
     * 查询有多少天的排班数据
     *
     * @param criteria 查询条件
     * @return 天数
     */
    private Integer countDays(Criteria criteria) {
        final String DATES_KEY = "dates";// 存放天数的键
        Query query = Query.query(criteria);

        ProjectionOperation inclusionOperation = new ProjectionOperation()
                .andInclude(ScheduleConstants.WORK_DATE)// 结果集中只需要工作日期
                .andExclude(Fields.UNDERSCORE_ID);// 排除 _id

        GroupOperation addToSetOperation = Aggregation.group("null")// 这里是 $null ，，不是 null ，等于将结果集又拷贝了一份，没有分组，浪费内存
                .addToSet(ScheduleConstants.WORK_DATE).as(DATES_KEY);// 将所有工作日期去重后加入一个临时数组

        ProjectionOperation sizeOperation = new ProjectionOperation()
                .and(DATES_KEY).size().as(MongoCountDto.COUNT_KEY)// 获取数组长度
                .andExclude(Fields.UNDERSCORE_ID);// 忽略 _id 字段

        Aggregation aggregation = Aggregation.newAggregation(inclusionOperation, addToSetOperation, sizeOperation);
        AggregationResults<MongoCountDto> results = this.mongoTemplate.aggregate(aggregation,
                ScheduleConstants.COLLECTION_NAME, MongoCountDto.class);
        return results.getUniqueMappedResult().getCount();
    }

    @Override
    public ScheduleQueryVo findById(String id) {
        return this.mongoTemplate.findById(id, ScheduleQueryVo.class, ScheduleConstants.COLLECTION_NAME);
    }

    @Override
    public boolean updateNumberById(OrderMessageVo order) {
        Criteria criteria = Criteria.where("_id").is(new ObjectId(order.getScheduleId()))
                .and(BaseEntityConstants.FIELD_IS_DELETED).is(BaseEntityConstants.NOT_DELETED);// 未删除
        Query query = Query.query(criteria);

        Update update = Update.update(ScheduleConstants.AVAILABLE_NUMBER, order.getAvailableNumber())
                .set(ScheduleConstants.RESERVED_NUMBER, order.getReservedNumber())
                .set(BaseEntityConstants.UPDATE_TIME, new Date());
        UpdateResult updateResult = this.mongoTemplate.updateFirst(query, update, ScheduleConstants.COLLECTION_NAME);
        System.out.println("更新排班数：" + updateResult.getModifiedCount());
        return updateResult.getModifiedCount() == 1L;
    }

}
